﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Harvest.API.Attributes;
using Harvest.API.Model;

namespace Harvest.API
{
    
    public static class Extensions
    {

        private static bool IsNullOrEmpty(this string input)
        {
            return String.IsNullOrEmpty(input);
        }



        internal static string Serialize(this Client client)
        {
            return SerializeObject(client);
        }
        internal static Client ToClient(this XDocument xDocument)
        {
            return DeserializeObject(xDocument, typeof(Client)) as Client;
        }
        internal static List<Client> ToClients(this XDocument xDocument)
        {
            return xDocument.Elements("clients").Elements("client").Select(xElement => (new XDocument(xElement)).ToClient()).ToList();
        }


        internal static string Serialize(this Contact contact)
        {
            return SerializeObject(contact);
        }
        internal static Contact ToContact(this XDocument xDocument)
        {
            return DeserializeObject(xDocument, typeof(Contact)) as Contact;
        }
        internal static List<Contact> ToContacts(this XDocument xDocument)
        {
            return xDocument.Elements("contacts").Elements("contact").Select(xElement => (new XDocument(xElement)).ToContact()).ToList();
        }


        internal static string Serialize(this Project project)
        {
            return SerializeObject(project);
        }
        internal static Project ToProject(this XDocument xDocument)
        {
            return DeserializeObject(xDocument, typeof(Project)) as Project;
        }
        internal static List<Project> ToProjects(this XDocument xDocument)
        {
            return xDocument.Elements("projects").Elements("project").Select(xElement => (new XDocument(xElement)).ToProject()).ToList();
        }


        internal static string Serialize(this Task task)
        {
            return SerializeObject(task);
        }
        internal static Task ToTask(this XDocument xDocument)
        {
            return DeserializeObject(xDocument, typeof(Task)) as Task;
        }
        internal static List<Task> ToTasks(this XDocument xDocument)
        {
            return xDocument.Elements("tasks").Elements("task").Select(xElement => (new XDocument(xElement)).ToTask()).ToList();
        }


        internal static string Serialize(this User user)
        {
            return SerializeObject(user);
        }
        internal static User ToUser(this XDocument xDocument)
        {
            return DeserializeObject(xDocument, typeof(User)) as User;
        }
        internal static List<User> ToUsers(this XDocument xDocument)
        {
            return xDocument.Elements("users").Elements("user").Select(xElement => (new XDocument(xElement)).ToUser()).ToList();
        }




        internal static bool? ToNullableBool(this XElement xElement)
        {
            if (IsNil(xElement)) return null;

            var value = (string) xElement;
            bool result;
            if (bool.TryParse(value, out result))
            {
                return result;
            }
            return null;
        }

        internal static int? ToNullableInt(this XElement xElement)
        {
            if (IsNil(xElement)) return null;

            var value = (string)xElement;
            int result;
            if (int.TryParse(value, out result))
            {
                return result;
            }
            return null;
        }

        internal static float? ToNullableFloat(this XElement xElement)
        {
            if (IsNil(xElement)) return null;

            var value = (string)xElement;
            float result;
            if (float.TryParse(value, out result))
            {
                return result;
            }
            return null;
        }

        internal static DateTime? ToNullableDateTime(this XElement xElement)
        {
            if (IsNil(xElement)) return null;

            var value = (string)xElement;
            DateTime result;
            if (DateTime.TryParse(value, out result))
            {
                return result;
            }
            return null;
        }


        /// <summary>
        /// Checks if a give XElement contains an attribute NIL and returns the boolean value.
        /// </summary>
        /// <param name="xElement"></param>
        /// <returns></returns>
        private static bool IsNil(XElement xElement)
        {
            if (xElement != null)
            {
                var nilAttribute = xElement.Attribute("nil");
                if (nilAttribute != null)
                {
                    bool temp;
                    if (bool.TryParse(nilAttribute.Value, out temp))
                    {
                        return temp;
                    }
                }
            }
            return false;
        }


        private static string SerializeObject(object inputObject)
        {

            // Get the type of the given object
            var type = inputObject.GetType();

            // Get custom SerializationInfoAttribute for class
            var classAttributes = type.GetCustomAttributes(typeof(SerializationInfoAttribute), true);
            var classAttribute = (SerializationInfoAttribute)classAttributes[0];
            var classNodeText = classAttribute.NodeText;

            // Create the root XDocument
            var xDocument = new XDocument(new XElement(classNodeText));

            // Iterate through all properties of the given object
            foreach (var propertyInfo in type.GetProperties())
            {

                // Get custom SerializationInfoAttribute for property
                var propAttributes = propertyInfo.GetCustomAttributes(typeof(SerializationInfoAttribute), true);
                if (propAttributes.Length != 0)
                {
                    var propAttribute = (SerializationInfoAttribute)propAttributes[0];
                    var propNodeText = propAttribute.NodeText;
                    var propNodeValue = propertyInfo.GetValue(inputObject, null);

                    var newXElement = new XElement(propNodeText, propNodeValue);

                    if (!propAttribute.DataType.IsNullOrEmpty())
                    {
                        var newDataTypeXAttribute = new XAttribute("type", propAttribute.DataType);
                        newXElement.Add(newDataTypeXAttribute);
                    }

                    if (propAttribute.IsNullable && propNodeValue == null)
                    {
                        var newIsNullableXAttribute = new XAttribute("nil", "true");
                        newXElement.Add(newIsNullableXAttribute);
                    }

                    var rootNode = xDocument.Element(classNodeText);
                    if (rootNode != null)
                    {
                        rootNode.Add(newXElement);
                    }
                }
            }

            return xDocument.ToString();

        }

        private static object DeserializeObject(XDocument xDocument, Type type)
        {

            // Create instance of object type to populate and return
            var obj = Activator.CreateInstance(type);
            
            // Get root node
            var rootNode = xDocument.FirstNode as XElement;

            if (rootNode != null)
            {

                // Iterate through all properties of newly created object
                foreach (var propertyInfo in type.GetProperties())
                {
                    // Get custom SerializationInfoAttribute for property
                    var propAttributes = propertyInfo.GetCustomAttributes(typeof (SerializationInfoAttribute), true);
                    if (propAttributes.Length != 0)
                    {
                        var propAttribute = (SerializationInfoAttribute) propAttributes[0];
                        var propNodeText = propAttribute.NodeText;

                        //
                        var valueNode = rootNode.Element(propNodeText);
                        if (valueNode != null)
                        {
                            Type propertyType = propertyInfo.PropertyType;
                            Type typeToCastTo = propertyType;
                            if (propertyType.IsGenericType)
                            {
                                Type genericPropertyType = propertyType.GetGenericTypeDefinition();
                                if (genericPropertyType.Equals(typeof (Nullable<>)))
                                {
                                    // Nullable
                                    typeToCastTo = Nullable.GetUnderlyingType(propertyType);
                                }
                            }
                            object castedValue;
                            if (valueNode.Value != "")
                            {
                                castedValue = Convert.ChangeType(valueNode.Value, typeToCastTo);
                            }
                            else
                            {
                                if (typeToCastTo == typeof (string))
                                {
                                    castedValue = "";
                                }
                                else
                                {
                                    var defaultValueForType = Activator.CreateInstance(propertyType);
                                    castedValue = defaultValueForType;
                                        // Convert.ChangeType(defaultValueFoType, propertyType);
                                }
                            }
                            propertyInfo.SetValue(obj, castedValue, null);
                        }

                    }
                }
            }

            return obj;
        }
    }

}
